<!DOCTYPE html>
  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Face Recognition</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
  <script src='https://fustyles.github.io/webduino/TensorFlow/Face-api/face-api.min.js'></script>
  </head>
  <body>
  <div id="container"></div>
  <img id="ShowImage" src="" style="display:none" crossorigin="anonymous">
  <canvas id="canvas"></canvas>
  <canvas id="canvas_detect" style="display:none"></canvas>
  <table>
  <tr>
    <td><button id="restart" onclick="try{fetch(document.location.origin+'/control?restart');}catch(e){}">Restart</button></td> 
    <td><input type="button" id="getStill" value="Get Still"></td>
    <td><input type="checkbox" id="detect">detect</td> 
  </tr>
  <tr>
    <td>MirrorImage</td> 
    <td colspan="2">  
      <select id="mirrorimage">
        <option value="1">Y</option>
        <option value="0">N</option>
      </select>
    </td>
  </tr>       
  <tr>
    <td>Flash</td>
    <td colspan="2"><input type="range" id="flash" min="0" max="255" value="0"></td>
  </tr>
  <tr>
    <td>Quality</td>
    <td colspan="2"><input type="range" id="quality" min="10" max="63" value="10"></td>
  </tr>
  <tr>
    <td>Brightness</td>
    <td colspan="2"><input type="range" id="brightness" min="-2" max="2" value="0"></td>
  </tr>
  <tr>
    <td>Contrast</td>
    <td colspan="2"><input type="range" id="contrast" min="-2" max="2" value="0"></td>
  </tr>
  <tr>
    <td>Resolution</td> 
    <td colspan="2">
      <select id="framesize">
        <option value="10">UXGA(1600x1200)</option>
        <option value="9">SXGA(1280x1024)</option>
        <option value="8">XGA(1024x768)</option>
        <option value="7">SVGA(800x600)</option>
        <option value="6">VGA(640x480)</option>
        <option value="5">CIF(400x296)</option>
        <option value="4" selected="selected">QVGA(320x240)</option>
        <option value="3">HQVGA(240x176)</option>
        <option value="0">QQVGA(160x120)</option>
      </select> 
    </td>
  </tr>
  <tr>
    <td>Rotate</td>
    <td align="left" colspan="2">
        <select onchange="document.getElementById('canvas').style.transform='rotate('+this.value+')';">
          <option value="0deg">0deg</option>
          <option value="90deg">90deg</option>
          <option value="180deg">180deg</option>
          <option value="270deg">270deg</option>
        </select>
    </td>
  </tr>  
  </table>
  <div id="message" style="color:red">Please wait for loading model.<div>
  </body>
  </html> 
  
  <script>
    const faceImagesPath = 'https://fustyles.github.io/webduino/TensorFlow/Face-api/facelist/'; 
    const faceLabels = ['France', 'Terry'];
    faceImagesCount = 2 ;
    // LabelName/n.jpg  -->  France/1.jpg, France/2.jpg, Terry/1.jpg, Terry/2.jpg
    const distanceLimit = 0.4;
    
    const modelPath = 'https://fustyles.github.io/webduino/TensorFlow/Face-api/';
    //Model: https://github.com/fustyles/webduino/tree/master/TensorFlow/Face-api
    let displaySize = { width:320, height: 240 }
    let labeledFaceDescriptors;
    let faceMatcher;    
    
	var restart = document.getElementById('restart');
    var getStill = document.getElementById('getStill');
    var ShowImage = document.getElementById('ShowImage');
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var canvas_detect = document.getElementById("canvas_detect");
    var context_detect = canvas_detect.getContext("2d");    
    var detect = document.getElementById('detect'); 
    var mirrorimage = document.getElementById("mirrorimage");  
    var message = document.getElementById('message');
    var flash = document.getElementById('flash'); 
    var myTimer;
    var restartCount=0;

    Promise.all([
      faceapi.nets.faceLandmark68Net.load(modelPath),
      faceapi.nets.faceRecognitionNet.load(modelPath),
      faceapi.nets.ssdMobilenetv1.load(modelPath)
    ]).then(function(){
      message.innerHTML = "";
      getStill.click();
    })
    
    getStill.onclick = function (event) {
      clearInterval(myTimer);  
      myTimer = setInterval(function(){error_handle();},5000);
      ShowImage.src=location.origin+'/capture?'+Math.random();
    }
    function error_handle() {
      restartCount++;
      clearInterval(myTimer);
      if (restartCount<=2) {
        message.innerHTML = "Get still error. <br>Restart ESP32-CAM "+restartCount+" times.";
        myTimer = setInterval(function(){getStill.click();},10000);
      }
      else
        message.innerHTML = "Get still error. <br>Please close the page and check ESP32-CAM.";
    }    
    ShowImage.onload = function (event) {
      clearInterval(myTimer);
      restartCount=0;      
      canvas.setAttribute("width", ShowImage.width);
      canvas.setAttribute("height", ShowImage.height); 
      
      if (mirrorimage.value==1) {
        context.translate((canvas.width + ShowImage.width) / 2, 0);
        context.scale(-1, 1);
        context.drawImage(ShowImage, 0, 0, ShowImage.width, ShowImage.height);
        context.setTransform(1, 0, 0, 1, 0, 0);
      }
      else
        context.drawImage(ShowImage,0,0,ShowImage.width,ShowImage.height);
      
      DetectImage();       
    }     
    
    restart.onclick = function (event) {
      fetch(location.origin+'/control?restart');
    }    
    flash.onchange = function (event) {
      fetch(location.origin+'/control?flash='+this.value);
    } 
    framesize.onclick = function (event) {
      fetch(document.location.origin+'/control?var=framesize&val='+this.value);
      setTimeout(function(){getStill.click();},1000);
    }  
    quality.onclick = function (event) {
      fetch(document.location.origin+'/control?var=quality&val='+this.value);
    } 
    brightness.onclick = function (event) {
      fetch(document.location.origin+'/control?var=brightness&val='+this.value);
    } 
    contrast.onclick = function (event) {
      fetch(document.location.origin+'/control?var=contrast&val='+this.value);
    }  
	
    async function DetectImage() {
      if (detect.checked) {
        canvas.style.display="none";
        canvas_detect.style.display="block";
        canvas_detect.setAttribute("width", canvas.width);
        canvas_detect.setAttribute("height", canvas.height); 
        context_detect.drawImage(canvas,0,0,canvas.width,canvas.height);
        let displaySize = { width:canvas.width, height: canvas.height }
  
        if (!labeledFaceDescriptors) {
          message.innerHTML = "Loading face images...";      
          labeledFaceDescriptors = await loadLabeledImages();
          faceMatcher = new faceapi.FaceMatcher(labeledFaceDescriptors, distanceLimit)
        }
        const detections = await faceapi.detectAllFaces(canvas_detect).withFaceLandmarks().withFaceDescriptors();
        const resizedDetections = faceapi.resizeResults(detections, displaySize);
  
        const results = resizedDetections.map(d => faceMatcher.findBestMatch(d.descriptor));
        message.innerHTML = JSON.stringify(results);
      
        results.forEach((result, i) => {
          const box = resizedDetections[i].detection.box
          var drawBox;
          //if (result.distance<=distanceLimit)
            drawBox = new faceapi.draw.DrawBox(box, { label: result.toString()})
          //else
          //  drawBox = new faceapi.draw.DrawBox(box, { label: (Math.round(result.distance*100)/100).toString()})
          drawBox.draw(canvas_detect);
        }) 
      }
      else {
        canvas.style.display="block";
        canvas_detect.style.display="none";
      }
            
      try { 
        document.createEvent("TouchEvent");
        setTimeout(function(){getStill.click();},250);
      }
      catch(e) { 
        setTimeout(function(){getStill.click();},150);
      }         
    }   

    function loadLabeledImages() {
      return Promise.all(
        faceLabels.map(async label => {
          const descriptions = []
          for (let i=1;i<=faceImagesCount;i++) {
            const img = await faceapi.fetchImage(faceImagesPath+label+'/'+i+'.jpg')
            const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
            descriptions.push(detections.descriptor)
          }
          return new faceapi.LabeledFaceDescriptors(label, descriptions)
        })
      )
    }     
  </script>   
